/*
The MIT License (MIT)
Copyright (c) 2013 Dalvik Boss dalvikboss@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package sidl.generator.c;

import sidl.Clazz;
import sidl.Field;
import sidl.Func;
import sidl.Param;
import sidl.generator.Generator;
import sidl.generator.GeneratorContext;

public class AnsicGenerator extends Generator {

    public AnsicGenerator(GeneratorContext context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onGenerate() throws Throwable {
        includes("stdio.h");
        includes("stdlib.h");
        includes("string.h");
        includes("sys/socket.h");
        includes("unistd.h");
        includes("sys/types.h");
        includes("netinet/in.h");
        includes("time.h");
        includes("netdb.h");
        includes("errno.h");
        includes("string.h");
        if(doc.isAndroidIpc()) {
            includes("sys/un.h");
        }

        includes(doc.packageName.replace('.', '_') + '_' + doc.mainInf().name + ".h");
        generateDefines();
        new MemoryFunctionsProtoTypeGenerator(context).generate();

        println("typedef enum io_status {");
        println("    is_failed = 0,");
        println("    is_ok,");
        println("}io_status;");

        println("");
        println("static io_status agent_connect_of_%s(agent_context *agentctx);", doc.cPackageName());
        println("static void agent_close_of_%s_%s(agent_context *agentctx);", doc.cPackageName(), doc.mainInf().name);

        println("#ifdef MMDBG");
        println("static void *mem_alloc_debug_of_%s_%s(io_context *ioctx, int size, const char *filename, const char *function, int lineno);", doc.cPackageName(), doc.mainInf().name);
        println("#else                                    ");
        println("static void *mem_alloc_of_%s_%s(io_context *ioctx, int size) ; ", doc.cPackageName(), doc.mainInf().name);
        println("#endif");

        generateSendFieldXXXXXFunctionProtoTypes();
        generateFunctions();
        generateSendFieldXXXXXFunctionBodies();
        new MemoryFunctionsGenerator(context).generate();

        println("static io_status agent_connect_of_%s(agent_context *agentctx) {                                         ", doc.cPackageName());
        println("    int sockfd, size;  ");
        if(doc.isAndroidIpc()) {
            println("    struct sockaddr_un un;");
        }
        
        if(doc.isAndroidIpc()) {
            println("    memset(&un, 0, sizeof(un));");
            println("    un.sun_family = AF_UNIX;");
            println("    sprintf(un.sun_path, \"%%s%%d\", agentctx->name, getpid());");
            println("    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);");
            println("    if(sockfd < 0) {");
            println("        throw(strerror(errno));");
            println("    }");
            println("    size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);");
            println("    if (bind(sockfd, (struct sockaddr *)&un, size) < 0) {");
            println("        throw(strerror(errno));");
            println("    }");
            println("    memset(&un, 0, sizeof(un));");
            println("    un.sun_family = AF_UNIX;");
            println("    strcpy(un.sun_path, agentctx->name);");
            println("    if (connect(sockfd, (struct sockaddr *)&un, size) == -1) {        ");
            println("        throw(strerror(errno));                                                                  ");
            println("    }            ");
            
        } else {
            println("    struct hostent *host; ");
            println("    struct sockaddr_in serv_addr;");
            println("    host = gethostbyname(agentctx->ip);   ");
            println("               ");
            println("    if(!host) {                                                                                ");
            println("        throw(GET_HOST_BY_NAME_ERROR);                                                         ");
            println("    }                                                                                          ");
            println("    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){                                     ");
            println("        throw(SOCKET_ERROR);                                                                   ");
            println("    }                                                                                          ");
            println("    serv_addr.sin_family=AF_INET;                                                              ");
            println("    serv_addr.sin_port=htons(agentctx->port);                                                            ");
            println("    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);                                    ");
            println("    bzero(&(serv_addr.sin_zero),8);                                                            ");
            println("    if (connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr)) == -1) {        ");
            println("        throw(CONNECT_ERROR);                                                                  ");
            println("    }                                                                                          ");
            
        }
        println("    agentctx->ioctx.fd = sockfd;");
        println("    return is_ok;                                                                              ");
        println("}");

        println("static void agent_close_of_%s_%s(agent_context *agentctx) {                                         ", doc.cPackageName(), doc.mainInf().name);
        println("    close(agentctx->ioctx.fd);");
        println("}");

        println("call_status clean_of_%s_%s(agent_context *agentctx) {", doc.cPackageName(), doc.mainInf().name);
        println("    int i = 0;");
        println("    agentctx->status = ct_ok;");
        println("    agentctx->throwable = 0;");
        println("    for(i = 0; i < agentctx->ioctx.mem.used; i++) {");
        println("        free(agentctx->ioctx.mem.blocks[i].p);");
        println("    }");
        println("    memset(&agentctx->ioctx,0,sizeof(io_context));");
        println("    return ct_ok;");
        println("}");

    }

    private void generateDefines() {
        println("#define ARRAY_MAX 1024");
        println("#define NULL_OBJECT -1");
        println("#define NULL_ARRAY -1");

        println("#define trace printf");
        println("#define try_io(io_stmt) \\");
        println("do { \\");
        println("    if(is_ok!=(io_stmt)) {\\");
        println("        return is_failed; \\");
        println("    } \\");
        println("} while(0)");
        println("");
        println("#define try_non_null(ptr) \\");
        println("do { \\");
        println("    if(!(ptr)) {\\");
        println("        return is_failed; \\");
        println("    } \\");
        println("} while(0)");

        println("#define try_call(io_stmt) \\");
        println("do { \\");
        println("    if(!(io_stmt)) {\\");
        println("        goto _call_failed; \\");
        println("    } \\");
        println("} while(0)");

        println("#define throw(msg) \\");
        println("do { \\");
        println("    trace(\"%%s(%%d): %%s %%s\\n\", __FILE__,__LINE__,__FUNCTION__, msg); \\");
        println("    return is_failed;\\");
        println("}while(0)");

        println("#define GET_HOST_BY_NAME_ERROR \"get host by name error\"");
        println("#define SOCKET_ERROR \"socket error\"");
        println("#define CONNECT_ERROR \"connect error\"");
        println("#define BAD_ARRAY_SIZE \"bad array size\"");
        println("#define BAD_STRING_SIZE \"bad string size\"");

        println("#define SEND_BROKEN \"send broken\"");
        println("#define RECV_BROKEN \"recv broken\"");
        println("#define NO_MEMORY \"no memory\"");
        println("#define OUTOFBUF_ERROR \"out of buff error\"");

    }

    private void generateSendFieldXXXXXFunctionProtoTypes() {
        for (Clazz clz : doc.clazzes) {
            println("static io_status write_%s_%s(io_context *ioctx, %s *v);", doc.cPackageName(), clz.name, clz.name);
            println("static io_status write_%s_%s_array(io_context *ioctx, %s_array *v);", doc.cPackageName(), clz.name, clz.name);
        }
        for (Clazz clz : doc.clazzes) {
            println("static io_status read_%s_%s(io_context *ioctx, %s **v);", doc.cPackageName(), clz.name, clz.name);
            println("static io_status read_%s_%s_array(io_context *ioctx, %s_array **v);", doc.cPackageName(), clz.name, clz.name);
        }

        println("static io_status write_%s_cstring(io_context *ioctx, cstring *v);", doc.cPackageName());
        println("static io_status write_%s_cstring_array(io_context *ioctx, cstring_array *v);", doc.cPackageName());
        println("static io_status read_%s_cstring(io_context *ioctx, cstring **v);", doc.cPackageName());
        println("static io_status read_%s_cstring_array(io_context *ioctx, cstring_array **v);", doc.cPackageName());

        println("static io_status write_%s_int(io_context *ioctx, int v);", doc.cPackageName());
        println("static io_status write_%s_int_array(io_context *ioctx, int_array *v);", doc.cPackageName());
        println("static io_status read_%s_int(io_context *ioctx, int *v);", doc.cPackageName());
        println("static io_status read_%s_int_array(io_context *ioctx, int_array **v);", doc.cPackageName());
        println("static io_status recv_fully(io_context *ioctx, char *data, int off, int len);");
        println("static io_status send_fully(io_context *ioctx, char *data, int off, int len);");
        println("static io_status send_commit(io_context *ioctx);");
        println("static io_status recv_all(io_context *ioctx);");

    }

    private void generateSendFieldXXXXXFunctionBodies() {
        for (Clazz clz : doc.clazzes) {
            println("static io_status write_%s_%s(io_context *ioctx, %s *v) {", doc.cPackageName(), clz.name, clz.name);
            println("    if(!v) {");
            println("        try_io(write_%s_int(ioctx, NULL_OBJECT));", doc.cPackageName());
            println("        return is_ok;");
            println("    };");

            for (Field f : clz.fields) {
                println("    try_io(write_%s_%s(ioctx, v->%s));", doc.cPackageName(), f.type.toC(), f.name);
            }
            println("    return is_ok;");
            println("}");

            generate_write_xxxxxx_array(clz.name);
        }

        for (Clazz clz : doc.clazzes) {
            println("static io_status read_%s_%s(io_context *ioctx, %s **v) {", doc.cPackageName(), clz.name, clz.name);
            println("    int oflag = 0;");
            println("    try_io(read_%s_int(ioctx, &oflag));", doc.cPackageName());
            println("    if(oflag==-1) {");
            println("        *v = NULL;");
            println("        return is_ok;");
            println("    }");
            println("    *v = mem_alloc(ioctx, sizeof(%s));", clz.name);
            println("    if(!*v) {");
            println("        throw(\"no memory\");");
            println("    }");
            println("    memset(*v, 0, sizeof(%s));", clz.name);
            for (Field f : clz.fields) {
                println("    try_io(read_%s_%s(ioctx, &((*v)->%s)));", doc.cPackageName(), f.type.toC(), f.name);
            }
            println("    return is_ok;");
            println("}");

            generate_read_xxxxxx_array(clz.name);

        }

        println("static io_status read_%s_cstring(io_context *ioctx, cstring **v) {", doc.cPackageName());
        println("    int size = 0;");
        println("    try_io(read_%s_int(ioctx, &size));", doc.cPackageName());
        println("    if(size==-1) {");
        println("        *v = NULL;");
        println("        return is_ok;");
        println("    }");
        println("    if((size<-1) || (size > ARRAY_MAX)) {");
        println("        throw(BAD_STRING_SIZE);");
        println("    }");
        println("    *v = mem_alloc(ioctx, sizeof(cstring));");
        println("    if(!*v) {");
        println("        throw(NO_MEMORY);");
        println("    }");
        println("    memset(*v, 0, sizeof(cstring));");
        println("    (*v)->size = size;");
        println("    (*v)->data = malloc(size);");
        println("    if(!(*v)->data) {");
        println("        throw(NO_MEMORY);");
        println("    }");
        println("    try_io(recv_fully(ioctx,(*v)->data,0,size));");
        println("    return is_ok;");
        println("}");

        println("static io_status write_%s_cstring(io_context *ioctx, cstring *v) {", doc.cPackageName());
        println("    if(!v) {");
        println("        try_io(write_%s_int(ioctx, NULL_OBJECT));", doc.cPackageName());
        println("        return is_ok;");
        println("    }");
        println("    if((v->size<-1) || (v->size > ARRAY_MAX)) {");
        println("        throw(BAD_ARRAY_SIZE);");
        println("    }");
        println("    try_io(write_%s_int(ioctx, v->size));", doc.cPackageName());
        println("    try_io(send_fully(ioctx, v->data, 0, v->size));");
        println("    return is_ok;");
        println("}");

        generate_write_xxxxxx_array("cstring");
        generate_read_xxxxxx_array("cstring");

        println("static io_status read_%s_int(io_context *ioctx, int *v) {", doc.cPackageName());
        println("    char buf[4];");
        println("    try_non_null(v);");
        println("    try_io(recv_fully(ioctx, buf,0,4));");
        println("    *v = (buf[3] & 0x000000ff) | ((buf[2] << 8) & 0x0000ff00) | ((buf[1] << 16) & 0x00ff0000) | ((buf[0] << 24) & 0xff000000);");
        println("    return is_ok;");
        println("}");

        println("static io_status read_%s_int_array(io_context *ioctx, int_array **v) {", doc.cPackageName());
        println("    int arraylen = 0, i = 0;");
        println("    try_io(read_%s_int(ioctx, &arraylen));", doc.cPackageName());
        println("    if(arraylen==-1) {");
        println("        *v = NULL;");
        println("        return is_ok;");
        println("    }");
        println("    if((arraylen<-1)||(arraylen>ARRAY_MAX)) {");
        println("        throw(BAD_ARRAY_SIZE);");
        println("    }");
        println("    *v = mem_alloc(ioctx, sizeof(int_array));");
        println("    if(!*v) {");
        println("        throw(NO_MEMORY);");
        println("    }");
        println("    memset(*v, 0, sizeof(int_array));");
        println("    (*v)->size = arraylen;");

        println("    (*v)->elems = mem_alloc(ioctx, sizeof(int) * arraylen);");
        println("    if(!(*v)->elems) {");
        println("        throw(NO_MEMORY);");
        println("    }");

        println("    memset((*v)->elems, 0, sizeof(int) * arraylen);");

        println("    for(i = 0; i < arraylen; i++) {");
        println("        try_io(read_%s_int(ioctx, &((*v)->elems[i])));", doc.cPackageName());
        println("    }");
        println("    return is_ok;");
        println("}");

        println("static io_status write_%s_int(io_context *ioctx, int v) {     ", doc.cPackageName());
        println("    char buf[4];                                           ");
        println("    buf[3] = (char)(v & 0x000000ff);                       ");
        println("    buf[2] = (char)((v >> 8)  & 0x000000ff);               ");
        println("    buf[1] = (char)((v >> 16) & 0x000000ff);               ");
        println("    buf[0] = (char)((v >> 24) & 0x000000ff);               ");
        println("    try_io(send_fully(ioctx, buf, 0, 4));                  ");
        println("    return is_ok;                                          ");
        println("}                                                          ");

        println("static io_status write_%s_int_array(io_context *ioctx, int_array *v) {", doc.cPackageName());
        println("    int i = 0;");
        println("    if(!v) {");
        println("        try_io(write_%s_int(ioctx, NULL_ARRAY));", doc.cPackageName());
        println("    }");
        println("    if((v->size < -1) || (v->size > ARRAY_MAX)) {");
        println("        throw(BAD_ARRAY_SIZE);");
        println("    }");
        println("    try_io(write_%s_int(ioctx, v->size)); //array size", doc.cPackageName());
        println("    for(i = 0; i< v->size; i++) {");
        println("        try_io(write_%s_int(ioctx, v->elems[i]));", doc.cPackageName());
        println("    }");
        println("    return is_ok;");
        println("}");



        println("");
        println("static io_status recv_all(io_context *ioctx) {");
        println("    ioctx->buff.pos = 0;");
        println("    int to_read = 4;");
        println("    int totaldata = 0;");
        println("    char intbuf[4];");
        println("    while(to_read > 0) {");
        println("        int r = recv(ioctx->fd, &intbuf[4 - to_read], to_read, 0);");
        println("        if(r <= 0) {");
        println("            break;");
        println("        }");
        println("        to_read -= r;");
        println("    }");
        println("    if(to_read!=0) {");
        println("        throw(RECV_BROKEN);");
        println("    }");
        println("    totaldata = (intbuf[3] & 0x000000ff) | ((intbuf[2] << 8) & 0x0000ff00) | ((intbuf[1] << 16) & 0x00ff0000) | ((intbuf[0] << 24) & 0xff000000);");
        println("    to_read = totaldata;");
        println("    if(to_read > BUFF_MAX) {");
        println("        throw(OUTOFBUF_ERROR);");
        println("    }");
        println("    while(to_read > 0) {");
        println("        int r = recv(ioctx->fd, &ioctx->buff.data[totaldata - to_read], to_read, 0);");
        println("        if(r <= 0) {");
        println("            break;");
        println("        }");
        println("        to_read -= r;");
        println("    }");
        println("    if(to_read!=0) {");
        println("        throw(RECV_BROKEN);");
        println("    }");
        println("    return is_ok;");
        println("}");
        println("static io_status recv_fully(io_context *ioctx, char *data, int off, int len) {");
        println("    if(ioctx->buff.pos + len > BUFF_MAX) {");
        println("        throw(OUTOFBUF_ERROR);");
        println("    }");
        println("    memcpy(&data[off], &ioctx->buff.data[ioctx->buff.pos], len);");
        println("    ioctx->buff.pos += len;");
        println("    return is_ok;");
        println("}");
        println("");
        
        
        
        

        println("static io_status send_commit(io_context *ioctx) {");
        println("    int len = ioctx->buff.pos + 4;");
        println("    int to_send = len;");
        println("");
        println("    ioctx->buff.data[3] = (char)(ioctx->buff.pos & 0x000000ff);");
        println("    ioctx->buff.data[2] = (char)((ioctx->buff.pos >> 8)  & 0x000000ff);");
        println("    ioctx->buff.data[1] = (char)((ioctx->buff.pos >> 16) & 0x000000ff);");
        println("    ioctx->buff.data[0] = (char)((ioctx->buff.pos >> 24) & 0x000000ff);");
        println("");
        println("    while(to_send > 0) {");
        println("        int r = send(ioctx->fd, &ioctx->buff.data[len - to_send], to_send, 0);");
        println("        if(r <= 0) {");
        println("            break;");
        println("        }");
        println("        to_send -= r;");
        println("    }");
        println("    if(to_send > 0) {");
        println("        throw(SEND_BROKEN);");
        println("    }");
        println("    return is_ok;");
        println("}");
        

        println("static io_status send_fully(io_context *ioctx, char *data, int off, int len) {       ");
        println("                                                                                     ");
        println("    if(ioctx->buff.pos+4+ len >= BUFF_MAX) {                                         ");
        println("        throw(OUTOFBUF_ERROR);                                                       ");
        println("    }                                                                                ");
        println("    memcpy(&ioctx->buff.data[ioctx->buff.pos+4], &data[off], len);                   ");
        println("    ioctx->buff.pos += len;                                                          ");
        println("    return is_ok;                                                                    ");
        println("}");

    }

    private void generate_read_xxxxxx_array(String typeName) {
        println("static io_status read_%s_%s_array(io_context *ioctx, %s_array **v) {", doc.cPackageName(), typeName, typeName);
        println("    int arraylen = 0, i = 0;");
        println("    try_io(read_%s_int(ioctx, &arraylen));", doc.cPackageName());
        println("    if(arraylen==-1) {");
        println("        *v = NULL;");
        println("        return is_ok;");
        println("    }");
        println("    if((arraylen<-1)||(arraylen>ARRAY_MAX)) {");
        println("        throw(BAD_ARRAY_SIZE);");
        println("    }");
        println("    *v = mem_alloc(ioctx, sizeof(%s_array));", typeName);
        println("    if(!*v) {");
        println("        throw(\"no memory\");");
        println("    }");
        println("    memset(*v, 0, sizeof(%s_array));", typeName);
        println("    (*v)->size = arraylen;");

        println("    (*v)->elems = mem_alloc(ioctx, sizeof(%s) * arraylen);", typeName);
        println("    if(!(*v)->elems) {");
        println("        throw(\"no memory\");");
        println("    }");
        println("    memset((*v)->elems, 0, sizeof(%s) * arraylen);", typeName);

        println("    for(i = 0; i < arraylen; i++) {");
        println("        try_io(read_%s_%s(ioctx, &((*v)->elems[i])));", doc.cPackageName(), typeName);
        println("    }");
        println("    return is_ok;");
        println("}");
    }

    private void generate_write_xxxxxx_array(String typeName) {
        println("static io_status write_%s_%s_array(io_context *ioctx, %s_array *v) {", doc.cPackageName(), typeName, typeName);
        println("    int i;");
        println("    if(!v) {");
        println("        try_io(write_%s_int(ioctx, NULL_ARRAY));", doc.cPackageName());
        println("        return is_ok;");
        println("    };");
        println("    if((v->size < -1) || (v->size > ARRAY_MAX)) {");
        println("        throw(BAD_ARRAY_SIZE);");
        println("    }");
        println("    try_io(write_%s_int(ioctx, v->size));", doc.cPackageName());
        println("    for(i=0; i < v->size; i++) {");
        println("        try_io(write_%s_%s(ioctx, v->elems[i]));", doc.cPackageName(), typeName);
        println("    }");
        println("    return is_ok;");
        println("}");
    }

    private void generateFunctions() {
        for (Func f : doc.mainInf().funcs) {

            if (f.params.size() == 0) {
                println("%s %s(agent_context *agentctx) {", f.rtype.asReturnTypeOfC(), c_function_name(f));
            } else {
                println("%s %s(agent_context *agentctx, %s) {", f.rtype.asReturnTypeOfC(), c_function_name(f), c_function_formal_argslist(f));
            }

            move();
            generateFunctonBody(f);
            bak();
            println("}");
        }

    }

    private void generateFunctonBody(Func f) {
        if (!f.rtype.isVoid()) {
            println("%s result = 0;", f.rtype.asReturnTypeOfC());
        }
        println("memset(&agentctx->ioctx, 0, sizeof(io_context));");
        println("agentctx->throwable = NULL;");
        println("agentctx->status = ct_failed;");

        println("try_call(agent_connect_of_%s(agentctx));", doc.cPackageName());
        println("try_call(write_%s_int(&agentctx->ioctx, ft_%s));", doc.cPackageName(), f.name);
        for (Param p : f.params) {
            println("try_call(write_%s_%s(&agentctx->ioctx, %s));", doc.cPackageName(), p.type.toC(), p.name);
        }
        println("try_call(send_commit(&agentctx->ioctx));");
        println("try_call(recv_all(&agentctx->ioctx));");

        println("try_call(read_%s_RemoteThrowable(&agentctx->ioctx, &agentctx->throwable));", doc.cPackageName());
        println("try_call(agentctx->throwable==NULL);");
        if (!f.rtype.isVoid()) {
            println("try_call(read_%s_%s(&agentctx->ioctx, &result));", doc.cPackageName(), f.rtype.toC());
        }

        println("agent_close_of_%s_%s(agentctx);", doc.cPackageName(), doc.mainInf().name);
        println("agentctx->status = ct_ok;");

        if (!f.rtype.isVoid()) {
            println("return result;");
        } else {
            println("return;");
        }
        bak();
        println("_call_failed:");
        move();
        println("agent_close_of_%s_%s(agentctx);", doc.cPackageName(), doc.mainInf().name);
        println("agentctx->status = ct_failed;");
        if (f.rtype.isVoid()) {
            println("return;");
        } else {
            println("return 0;");
        }
        println();

    }

    private void includes(String... inc) {
        for (String p : inc) {
            println("#include <%s>", p);
        }
    }

}
